#include <drivers/gpio.h>
#include <drivers/platform.h>
#include <list.h>
#include <types.h>
#include <string.h>
#include <stm32h7.h>
#include <irqnum.h>
#include <drivers/irq.h>
#include <config.h>
#include <io.h>


#define GPIO_CLOCK_BASE           (RCC_BASE + 0xE0)

#define GPIO_REG_SIZE             (0x400)
#define GPIO_BASE(x)              (D3_AHB1PERIPH_BASE + (GPIO_REG_SIZE * (x)))
#define GPIO_MODER_OFFSET         (0x00)
#define GPIO_OTYPER_OFFSET        (0x04)
#define GPIO_OSPEEDR_OFFSET       (0x08)
#define GPIO_PUPDR_OFFSET         (0x0C)
#define GPIO_IDR_OFFSET           (0x10)
#define GPIO_ODR_OFFSET           (0x14)
#define GPIO_BSRR_OFFSET          (0x18)
#define GPIO_LCKR_OFFSET          (0x1C)
#define GPIO_AFR_OFFSET           (0x20)

#define SYSCFG_EXTICR             ((SYSCFG_BASE) + 0x08)

#define EXIT_IMG_OFFSET           (0x80)
#define EXIT_EMR_OFFSET           (0x84)

#define EXIT_RTSR_OFFSET          (0x00)
#define EXIT_FTSR_OFFSET          (0x04)

#define APB4ENR_REG_OFFSET        (0xF4)
#define APB2ENR_SYSCFG_BIT        (1)


static void stm32h7_set_clock(int16_t pin, int status)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t base = GPIO_BASE(port);
    size_t ospr_val =  read32(base + GPIO_OSPEEDR_OFFSET);
    size_t clock_val = read32(GPIO_CLOCK_BASE);

    ospr_val |= (0x3 << (IO_TO_PIN(pin) << 1));
    write32(base + GPIO_OSPEEDR_OFFSET, ospr_val);
    
    if(status) {
        clock_val |= BIT(IO_TO_PORT(pin));
    }
    else {
        clock_val &= ~BIT(IO_TO_PORT(pin));
    }
    write32(GPIO_CLOCK_BASE, clock_val);
}


/**
 * MUX: |   High 8bit   |    low 8bit   |
 * FUN: |   pin mode    |      FUNC     |
 */
static void stm32h7_set_mux(int16_t pin, uint16_t mux)
{
    uint32_t port = IO_TO_PORT(pin);
    uint8_t offset = IO_TO_PIN(pin) & 0x7;
    uint32_t base = GPIO_BASE(port);

    uint32_t mode = read32(base + GPIO_MODER_OFFSET);
    mode &= ~(3 << (IO_TO_PIN(pin) << 1));
    mode |= (((mux >> 8) & 0x3) << (IO_TO_PIN(pin) << 1));
    write32(base + GPIO_MODER_OFFSET, mode);

    uint32_t afr = read32(base + GPIO_AFR_OFFSET + ((IO_TO_PIN(pin) >> 3) << 2));
    afr &= ~(0xF << (offset << 2));
    afr |= (((mux) & 0xF) << (offset << 2));
    write32(base + GPIO_AFR_OFFSET + ((IO_TO_PIN(pin) >> 3) << 2), afr);
}


static void stm32h7_set_dir(int16_t pin, uint8_t flag)
{
    uint32_t base = GPIO_BASE(IO_TO_PORT(pin));
    size_t modr_val = read32(base + GPIO_MODER_OFFSET);

    modr_val &= ~(3 << (IO_TO_PIN(pin) << 1));
    if(flag) {
        modr_val |= (1 << (IO_TO_PIN(pin) << 1));
    }

    write32(base + GPIO_MODER_OFFSET, modr_val);
}


static void stm32h7_set_drv(int16_t pin, uint8_t drv)
{
    GPIO_SYSLOG_WARN("%d, %d: this operation is not supported", pin, drv);
}


static void stm32h7_set_pulldown(int16_t pin, uint8_t flag)
{
    uint32_t base = GPIO_BASE(IO_TO_PORT(pin));
    size_t pupdr_val = read32(base + GPIO_PUPDR_OFFSET);

    pupdr_val &= ~(3 << (IO_TO_PIN(pin) << 1));
    if(flag) {
        pupdr_val |= (2 << (IO_TO_PIN(pin) << 1));
    }

    write32(base + GPIO_PUPDR_OFFSET, pupdr_val);
}


static void stm32h7_set_pullup(int16_t pin, uint8_t flag)
{
    uint32_t base = GPIO_BASE(IO_TO_PORT(pin));
    size_t pupdr_val = read32(base + GPIO_PUPDR_OFFSET);

    pupdr_val &= ~(3 << (IO_TO_PIN(pin) << 1));
    if(flag) {
        pupdr_val |= (1 << (IO_TO_PIN(pin) << 1));
    }

    write32(base + GPIO_PUPDR_OFFSET, pupdr_val);
}


static void stm32h7_set_value(int16_t pin, uint8_t value)
{
    uint32_t base = GPIO_BASE(IO_TO_PORT(pin));
    size_t bsrr_val = read32(base + GPIO_BSRR_OFFSET);

    if(value) {
        bsrr_val |= BIT(IO_TO_PIN(pin));
    }
    else {
        bsrr_val |= BIT(IO_TO_PIN(pin) + 16);
    }

    write32(base + GPIO_BSRR_OFFSET, bsrr_val);
}


static int stm32h7_get_value(int16_t pin)
{
    uint32_t base = GPIO_BASE(IO_TO_PORT(pin));

    return (read32(base + GPIO_IDR_OFFSET) & BIT(IO_TO_PIN(pin))) ? 1 : 0;
}


static int stm32h7_set_exti(int16_t pin, int flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint8_t gpio_pin = IO_TO_PIN(pin);

    write32(RCC_BASE + APB4ENR_REG_OFFSET, read32(RCC_BASE + APB4ENR_REG_OFFSET) | BIT(APB2ENR_SYSCFG_BIT));

    uint32_t exti = read32(SYSCFG_EXTICR + (gpio_pin >> 2) * 4);

    exti &= ~(0xf << ((gpio_pin & 0x3) << 2));
    exti |= (port << ((gpio_pin & 0x3) << 2));

    write32(SYSCFG_EXTICR + (gpio_pin >> 2) * 4, exti);

    write32(EXTI_BASE + EXIT_IMG_OFFSET, read32(EXTI_BASE + EXIT_IMG_OFFSET) | BIT(gpio_pin));

    if(flag & IRQF_RISING) {
        write32(EXTI_BASE + EXIT_RTSR_OFFSET, read32(EXTI_BASE + EXIT_RTSR_OFFSET) | BIT(gpio_pin));
    }
    if(flag & IRQF_FALLING) {
        write32(EXTI_BASE + EXIT_FTSR_OFFSET, read32(EXTI_BASE + EXIT_FTSR_OFFSET) | BIT(gpio_pin));
    }

    write32(GPIO_BASE(port) + GPIO_MODER_OFFSET, read32(GPIO_BASE(port) + GPIO_MODER_OFFSET) & (~(3 << (IO_TO_PIN(pin) << 1))));
    write32(GPIO_BASE(port) + GPIO_PUPDR_OFFSET, read32(GPIO_BASE(port) + GPIO_MODER_OFFSET) & (~(3 << (IO_TO_PIN(pin) << 1))));

    switch(gpio_pin) {
        case 0:  return EXTI0_IRQNUM;
        case 1: return EXTI1_IRQNUM;
        case 2: return EXTI2_IRQNUM;
        case 3: return EXTI3_IRQNUM;
        case 4: return EXTI4_IRQNUM;
        case 5 ... 9: return EXTI9_5_IRQNUM;
        case 10 ... 15: return EXTI15_10_IRQNUM;
        default: return -1;
    }

    return -1;
}


static struct gpio_ops stm32h7_ops = {
    .init = NULL,
    .set_clock = stm32h7_set_clock,
    .set_mux = stm32h7_set_mux,
    .set_dir = stm32h7_set_dir,
    .set_drv = stm32h7_set_drv,
    .set_pulldown = stm32h7_set_pulldown,
    .set_pullup = stm32h7_set_pullup,
    .set_value = stm32h7_set_value,
    .get_value = stm32h7_get_value,
    .set_exti = stm32h7_set_exti,
};


static struct gpio_desc stm32h7_gpio = {
    .name = "stm32h7-gpio",
    .npins = CONFIG_GPIO_PINS,
    .ops = &stm32h7_ops,
};


static int stm32h7_gpio_register(void)
{
    gpio_desc_register(&stm32h7_gpio);
    return 0;
}


base_initcall(stm32h7_gpio_register);
